// 底层Extract的定义：`type Extract<T, U> = T extends U ? T : never` 
// 保留 T 中符合 U 的部分
type A = Extract<number | string | boolean, number | string>  // type A = string | number

// type ObjectType<T> = T extends object ? T : never  // 不用再声明一个类型来用来判断，有内置的Extract类可以用
function cross<T, U>(obj1: Extract<T, object>, obj2: Extract<U, object>): T & U;
function cross<T, U, K>(obj1: Extract<T, object>, obj2: Extract<U, object>, obj3: Extract<K, object>): T & U & K;
// 为什么实现签名的obj3需要设置为可选，因为第一个重载签名声明了，可以只接受2个参数，所以后面的参数都是可选了。
function cross<T, U, K>(obj1: Extract<T, object>, obj2: Extract<U, object>, obj3?: Extract<K, object>): T & U & K {
  let obj = {} as T & U & K
  addObjProps(obj, obj1)
  addObjProps(obj, obj2)
  addObjProps(obj, obj3)
  return obj
}
// symbol属性不能用obj[key]还访问，所以设为any，而不是object
function addObjProps(obj: any , obj1: any) {
  for(let key in obj1) { obj[key] = obj1[key] }
}
type Obj1 = { name: string }
type Obj2 = { age: number }
let obj1: Obj1 = { name: "张三" }
let obj2: Obj2 = { age: 18 }

console.log(cross(obj1, obj2))

// Exclude：排除 `T` 中符合 `U` 的部分。
// type Exclude<T, U> = T extends U ? never : T
type B = Exclude<string | number | boolean, string | number>  // type B = boolean

// 作业：用Exclude实现，让 cross函数的形参不能是 Array类型
type ExcludeArr<T> = Exclude<T, Array<any> | string | number | boolean>
function cross2<T, U>(obj1: ExcludeArr<T>, obj2: ExcludeArr<U>): T & U;
function cross2<T, U>(obj1: ExcludeArr<T>, obj2: ExcludeArr<U>): T & U {
  let obj = {} as T & U
  addObjProps(obj, obj1)
  addObjProps(obj, obj2)
  return obj
};
let O1 = { name: "张三" } as { [key: string]: any }
let A1 = [1, 2]
// cross2(O1, A1)  // 会报错，参数不能是数组类型。

export {}